package com.haredot.filter;

import com.auth0.jwt.interfaces.DecodedJWT;
import com.haredot.properties.JwtProperties;
import com.haredot.utils.JwtUtils;
import com.haredot.utils.MD5Utils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.MediaType;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.security.authentication.AccountStatusUserDetailsChecker;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.ProviderNotFoundException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsChecker;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.rememberme.InvalidCookieException;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
import java.util.Base64;
import java.util.Map;

/**
 * JWT 认证 Filter
 * 根据用户传入的刷新令牌，换取令牌
 * (OncePerRequestFilter) 可以保证 每个请求 只会 过滤一次
 * <p>
 * AntPathRequestMatcher 基于 Ant 路径风格 的 请求 匹配器
 */
public class JWTAuthenticationFilter extends OncePerRequestFilter {
    /**
     * /api/token 是用来 根据用户名和密码换取 令牌的 网址
     */
    private static final AntPathRequestMatcher REFRESH_URL = new AntPathRequestMatcher("/api/token", "PUT");

    private final UserDetailsService userDetailService;

    private final UserDetailsChecker userDetailsChecker = new AccountStatusUserDetailsChecker();

    private final JwtProperties jwtProperties;

    private final StringRedisTemplate stringRedisTemplate ;

    public JWTAuthenticationFilter(UserDetailsService userDetailService, JwtProperties jwtProperties, StringRedisTemplate stringRedisTemplate) {
        this.userDetailService = userDetailService;
        this.jwtProperties = jwtProperties;
        this.stringRedisTemplate = stringRedisTemplate ;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        if (!REFRESH_URL.matches(request)) {
            // 继续向下执行
            filterChain.doFilter(request, response);
            return;
        }
        // 获取 刷新令牌
        String refreshToken = request.getParameter("refresh");

        try {
            if (!StringUtils.hasLength(refreshToken)) {
                throw new ProviderNotFoundException("refresh参数必传");
            }
            // 如果传入了 refreshToken , 解析验证刷新令牌
            DecodedJWT decodedJWT = JwtUtils.validToken(jwtProperties, refreshToken);
            String jwtId = decodedJWT.getId();
            String content = new String(Base64.getDecoder().decode(jwtId), StandardCharsets.UTF_8);
            String[] split = content.split(":");
            if (split.length != 3) {
                throw new InvalidCookieException("令牌唯一标识错误");
            }
            String uname = split[0]; // 获取用户名
            String exp = split[1]; // 获取过期时间
            String secure = split[2]; // 加密后的密文
            // 查询用户信息
            UserDetails user = userDetailService.loadUserByUsername(uname);

            String passwd = user.getPassword(); // 获取数据库中的密码
            String formatted = MessageFormat.format("{0}:{1}:{2}:{3}", uname, exp, passwd, jwtProperties.getKey());

            if (!MD5Utils.md5(formatted).equals(secure)) {
                throw new BadCredentialsException("用户名或密码不正确");
            }

            this.userDetailsChecker.check(user);

            JwtUtils.generatorToken(stringRedisTemplate, user, jwtProperties, response, true, true, false);
        } catch (AuthenticationException e) {
            Map<String, Object> result = Map.of("message", e.getMessage(), "status", false);
            new MappingJackson2HttpMessageConverter()
                    .write(result, MediaType.APPLICATION_JSON, new ServletServerHttpResponse(response));

        }

    }

}
